/**
 * @author Kevin Gomes
 */

import java.io.*;
import java.net.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.UUID;

import utils.*; //utils written for this project. Has MysqlConnect.java

/**
 * Class: PServer
 * 
 * Handles the server functionality of a Peer.
 */
public class Server {
    /**
     * Peer IP Address.
     */
    private String peer_ip;
    
    /**
     * DB Connection.
     */
    private MysqlConnect conn;
    
    /**
     * Array of Socket objects. This is used to store the client socket connections.
     */
    private Socket clientSockets[];

    /**
     * IP address of all the remote client that are connected to this peer.
     * This variable is used in conjunction with clientSockets[].
     */
    private String remoteIps[];

    /**
     * Server Socket instance
     */
    private ServerSocket serverSocket;

    /**
     * Stores all the descriptor IDs (Query & Query Hit) this server has seen.
     * Check 1: A Server will forward a Query Descriptor only once. Additional check in place to ensure this.
     * Check 2: The server will not do additional check before returning a QueryHit Descriptor to all the clients. 
     *   Due to Check 1, the problem of multiple QueryHit Descriptors arriving at a peer would not occur.
     */
    private String descriptorIds[];

    /**
     * An instance of the client.
     */
    private Client clientObj;

    /**
     * Constructor.
     */
    public void Server() { 

    }

    /**
     * Does all the initializing activities for the server.
     */
    public void init(Client clientObj) {
	//Save peer ip address
        this.peer_ip = this.getIp();

	//Setup a database connection	
	this.conn = new MysqlConnect();

	this.clientObj = clientObj;

	this.createSocket();
    }

    /**
     * Creates a server socket to which other peer clients can connect to.
     */
    public void createSocket() {
	try {
            InetAddress ownIP = InetAddress.getLocalHost();
	    this.serverSocket = new ServerSocket(4444,20,ownIP);

	    int count = 0;
	    while(true) {
		Socket clientSocket = this.serverSocket.accept();
		
		//Integer count = this.clientSockets.length;
		String remoteIp = clientSocket.getInetAddress().getHostAddress();
		this.remoteIps[count] = remoteIp;

		this.clientSockets[count] = clientSocket;
		count++;
		
		ServerThread thread = new ServerThread(clientSocket, this);
		thread.start();
	    }
	} catch (IOException e) {
	    e.printStackTrace();
	    System.exit(-1);
	}
    }

    /**
     * Returns the IP Address of the machine this client is running on.
     */
    public String getIp() {
        String ip = "";
        try{
            InetAddress ownIP = InetAddress.getLocalHost();
            ip = ownIP.getHostAddress();
        }catch (Exception e){
            System.out.println("Exception caught ="+e.getMessage());
        }
        return ip;
    }

    /**
     * Searches for a file name and returns whether it has the file or not.
     */
    public Boolean search(String fileName) {
	Boolean isFile = this.conn.checkPeerFile(this.peer_ip,fileName);
	return isFile;
    }

    /**
     * Returns the QueryHit descriptor.
     * 5acfdc70-8aa7-4b6e-9a3e-14f2e835d0ae;129;3;0;8"+"2;4444;192.168.1.1;0;Fileindex;filesize;filename;NULL
     */
    public String createQueryHitDescriptor(String ttl, String fileIndex, String filesize, String filename) {
	UUID val = UUID.randomUUID();
	String descriptorId = String.valueOf(val);
	String payloadDescriptor = "129";
        //String ttl = "3";
	String hop = "0";
	String payloadLength = "1";
	
	String searchCount = "1";
	String port = "4444";
	String ip = this.peer_ip;
	String speed = "0"; //Indicates max. speed

	String descriptor = descriptorId+";"+payloadDescriptor+";"+ttl+";"+hop+";"+payloadLength+";"+searchCount+";"+port+";"+ip+";"+speed+";"+fileIndex+";"+filesize+";"+filename+";NULL";

	return descriptor;
    }

    /**
     * 
     */
    public String fetchDescriptorId(String descriptor) {
	String tokens[] = descriptor.split(",");
	return tokens[0];
    }

    /**
     *
     */
    public void processQuery(String descriptor, String remoteIp) {
	//Accept Query descriptors
	//Store the descriptor ID
	//Forward the descriptor to the Client so that it can be sent to the neighbors.

	String tokens[] = descriptor.split(",");
	String descriptorId = tokens[0];
	String payloadDescriptor = tokens[1];
	int ttl = new Integer(tokens[2]);
	int hop = new Integer(tokens[3]);


	//if(this.descriptorIds[descriptorId] != null) {
	if(!this.isExistDescriptorId(descriptorId)) {
	    //Process each query only once.
	    //Process this descriptor only if it has not been processed before.

	    this.descriptorIds[this.descriptorIds.length+1] = descriptorId;
	    
	    //Forward Query only if TTL is not zero.
	    if(ttl != 0) {
		String newDescriptor = this.clientObj.reduceDescriptorTTL(descriptor);
		this.clientObj.talkToNeighbor(newDescriptor);
	    }
	}

	String filename = tokens[6];
	Boolean flag = this.search(filename);
	if(flag) {
	    //Create Query hit descriptor and send it to the client if file is found.
	    int newTTL = hop+1;
	    String fileIndex = "1";

	    long filesize = this.getFileSize(filename);
	    String newQhDescriptor = this.createQueryHitDescriptor(Integer.toString(newTTL),fileIndex,Long.toString(filesize),filename);

	    int key = getRemoteIpPos(remoteIp);
	    Socket clSocket = this.clientSockets[key];

	    try {
		PrintWriter out = new PrintWriter(clSocket.getOutputStream(), true);
		out.println(newQhDescriptor);
	    } catch (Exception e) {
		e.printStackTrace();
	    }
	}
    }

    /**
     * Returns whether a descriptor ID has been stored with this server.
     */
    public Boolean isExistDescriptorId(String descriptorId) {
	Boolean flag = false;
	for(int i = 0;i < this.descriptorIds.length;i++) {
	    if(descriptorId.equals(this.descriptorIds[i])) {
		flag = true;
	    }
	}
	return flag;
    }

    /**
     * TODO: Implement this function
     * Returns file size given by filename.
     */
    public long getFileSize(String filename) {
	long length = 0;
	try {
	    String path = new java.io.File(".").getCanonicalPath();
	    File file = new File(path+"/"+filename);
	    length = file.length();
	} catch(Exception e) {
	    e.printStackTrace();
	}
	return length;
    }

    /**
     * Forward the QueryHit Descriptor to all the Peer Clients, only if has processed the corresponding Query descriptor.
     */
    public void processQueryHitDescriptor(String descriptor) {
	String tokens[] = descriptor.split(",");
	String descriptorId = tokens[0];
	String payloadDescriptor = tokens[1];
	int ttl = new Integer(tokens[2]);
	int hop = new Integer(tokens[3]);

	if(this.isExistDescriptorId(descriptorId)) {
	    //Forward Query only if TTL is not zero.
	    if(ttl != 0) {
		String newQhDescriptor = this.clientObj.reduceDescriptorTTL(descriptor);

		for(int i = 0;i < this.clientSockets.length;i++) {
		    Socket clSocket = this.clientSockets[i];

		    try {
			PrintWriter out = new PrintWriter(clSocket.getOutputStream(), true);
			out.println(newQhDescriptor);
		    } catch(Exception e) {
			e.printStackTrace();
		    }
		}	       
	    }
	}
    }

    /**
     * Returns the position (index) of the remoteIp in the *this.remoteIps* array.
     */
    public int getRemoteIpPos(String remoteIp) {
	int i;
	for(i = 0;i < this.remoteIps.length;i++) {
	    String orig = this.remoteIps[i];
	    if(orig.equals(remoteIp)) {
		break;
	    }
	}
	return i;
    }
}